package cn.coderliu.voteweb.common.base;


import cn.coderliu.voteweb.common.resultEntity.SearchResult;
import cn.coderliu.voteweb.common.util.Reflections;
import cn.coderliu.voteweb.common.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by Administrator on 2014/12/29.
 */
@Transactional(rollbackFor = RuntimeException.class)
public abstract class BaseServiceImpl<T> implements BaseService<T> {
    /**
     * 实体类类型(由构造方法自动赋值)
     */
    private Class<?> entityClass;

    @Autowired
    JdbcTemplate jdbcTemplate;

    @PersistenceContext
    protected EntityManager entityManager;

    public BaseServiceImpl() {
        entityClass = Reflections.getClassGenricType(getClass());
    }

    @Override
    public List<T> findAll() {
        Query query = entityManager.createQuery("select e from " + getEntityName(entityClass) + " e", entityClass);
        return query.getResultList();
    }

    @Override
    public T get(Object id) {
        return entityManager.find((Class<T>) entityClass, id);
    }

    @Override
    public T save(T entity) {
        entityManager.flush();
        entityManager.persist(entity);
        return entity;
    }

    @Override
    public void delete(Long id) {
        delete(new Object[]{id});
    }

    @Override
    public T update(T entity) {
        entity = entityManager.merge(entity);
        return entity;
    }

    public <T> void delete(T entity) {
        entityManager.remove(entity);
    }

    public <T> void delete(Object[] entityids) {
        for (Object id : entityids) {
            entityManager.remove(entityManager.getReference(entityClass, id));
        }
    }

    protected <T> String getEntityName(Class<T> entityClass) {
        String entityName = entityClass.getSimpleName();
        Entity entity = entityClass.getAnnotation(Entity.class);
        if (entity.name() != null && !"".equals(entity.name())) {
            entityName = entity.name();
        }
        return entityName;
    }

    public void clear() {
        entityManager.clear();
    }

    public List<T> findList(String jql) {
        return findByList(jql, null);
    }

    public List<T> findByList(String jql, Map<String, Object> parameter) {
        Query query = entityManager.createQuery(jql, entityClass);
        if (parameter != null) {
            Set<String> keySet = parameter.keySet();
            for (String key : keySet) {
                query.setParameter(key, parameter.get(key));
            }
        }
        return query.getResultList();
    }

    @Override
    public SearchResult<T> findByPage(String jql, Integer pageNo) {
        return findByPage(jql, pageNo, null, null);
    }

    @Override
    public SearchResult<T> findByPage(String jql, Integer pageNo, Integer pageSize) {
        return findByPage(jql, pageNo, pageSize, null);
    }

    public SearchResult<T> findByPage(String jql, Integer pageNo, Integer pageSize, Map<String, Object> parameter) {
        SearchResult<T> searchResult ;
        if (StringUtil.isNotEmpty(pageSize)) {
            searchResult = new SearchResult<T>(pageSize);
        } else {
            searchResult = new SearchResult<T>();
        }
        long start = System.currentTimeMillis();
        String countSql = "select count(*) " + removeSelect(removeOrders(jql));
        Query countQuery = entityManager.createQuery(countSql, Long.class);
        if (parameter != null) {
            Set<String> keySet = parameter.keySet();
            for (String key : keySet) {
                countQuery.setParameter(key, parameter.get(key));
            }
        }
        Long totalCount = (Long) countQuery.getSingleResult();
        searchResult.setTotal(totalCount);
        searchResult.setCurrentPage(pageNo);
        Query query = entityManager.createQuery(jql, entityClass);
        if (parameter != null) {
            Set<String> keySet = parameter.keySet();
            for (String key : keySet) {
                query.setParameter(key, parameter.get(key));
            }
        }
        query.setFirstResult(searchResult.getFirstResult());
        query.setMaxResults(searchResult.getPageSize());

        List<T> data = query.getResultList();
        if(pageNo.intValue()<=searchResult.getTotalPages().intValue()){
            searchResult.setData(data);
        }
        return searchResult;
    }

    /**
     * 去除qlString的select子句。
     *
     * @param qlString
     * @return
     */
    private String removeSelect(String qlString) {
        int beginPos = qlString.toLowerCase().indexOf("from");
        return qlString.substring(beginPos);
    }

    /**
     * 去除hql的orderBy子句。
     *
     * @param qlString
     * @return
     */
    private String removeOrders(String qlString) {
        Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(qlString);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            m.appendReplacement(sb, "");
        }
        m.appendTail(sb);
        return sb.toString();
    }

    @Override
    public List<T> jpqlSearch(String jpql, Object... params) {
        Query query = entityManager.createQuery(jpql, entityClass);
        setParams(query, params);
        return query.getResultList();
    }

    @Transactional
    public int batchUpdate(String jpql, Object... params) {
        Query query = this.entityManager.createQuery(jpql);
        this.setParams(query, params);
        return query.executeUpdate();
    }

    private void setParams(Query query, Object[] params) {
        if (params != null) {
            for (int i = 0; i < params.length; ++i) {
                query.setParameter(i + 1, params[i]);
            }
        }
    }

    @Transactional
    public void batchInsert(List<T> items) {
        int i = 0;
        Iterator i$ = items.iterator();

        while (i$.hasNext()) {
            T item = (T) i$.next();
            this.entityManager.persist(item);
            ++i;
            if (i % 1000 == 0) {
                this.entityManager.flush();
                this.entityManager.clear();
            }
            this.entityManager.flush();
            this.entityManager.clear();
        }
    }

    @Transactional
    public void batchUpdate(List<T> items) {
        int i = 0;
        Iterator i$ = items.iterator();

        while (i$.hasNext()) {
            T item = (T) i$.next();
            this.entityManager.merge(item);
            ++i;
            if (i % 1000 == 0) {
                this.entityManager.flush();
                this.entityManager.clear();
            }

            this.entityManager.flush();
            this.entityManager.clear();
        }

    }


    @Override
    public Map<String, Object> searchListMapByPage(String sql, int pageNo, int pageSize) {
        List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
        Map<String, Object> map = this.listToJson(list, pageNo, pageSize);
        return map;
    }

    @Override
    public Map<String, Object> listToJson(List<Map<String, Object>> mapList, Integer pageIndex, Integer pageSize) {
        Map<String, Object> result = new HashMap<>(16);
        long total = mapList.size();
        result.put("totalCount", total);
        result.put("pageSize", pageSize);
        int totalPage = 0;
        if (total > 0) {
            totalPage = (int) (total % pageSize > 0 ? (total / pageSize + 1) : (total / pageSize));
        } else {
            totalPage = 1;

        }
        result.put("total", totalPage);
        result.put("currentPage", pageIndex);
        int currentIndex = (pageIndex - 1) * pageSize;
        List<Map<String, Object>> resultList = new LinkedList<Map<String, Object>>();
        if (pageIndex * pageSize <= total) {
            for (Map<String, Object> map : mapList.subList(currentIndex, pageIndex * pageSize)) {
                currentIndex++;
                map.put("index", currentIndex);
                resultList.add(map);
            }
            result.put("rows", resultList);
        } else {
            if (currentIndex > total) {
                result.put("currentPage", 1);
                currentIndex = 0;
            }
            for (Map<String, Object> map : mapList.subList(currentIndex, (int) total)) {
                currentIndex++;
                map.put("index", currentIndex);

                resultList.add(map);
            }
            result.put("rows", resultList);
        }
        return result;
    }


    @Override
    public SearchResult<T> entityListByPage(List<T> list, Integer pageNo, Integer pageSize, Map<String, Object> parameter) {
        SearchResult<T> searchResult = null;
        if (StringUtil.isNotEmpty(pageSize)) {
            searchResult = new SearchResult<T>(pageSize);
        } else {
            searchResult = new SearchResult<T>();
        }
        int total = list.size();
        searchResult.setTotal(Long.valueOf(total));
        int totalPage;
        if (total > 0) {
            totalPage = (int) (total % pageSize > 0 ? (total / pageSize + 1) : (total / pageSize));
        } else {
            totalPage = 1;

        }
        searchResult.setTotalPages(totalPage);
        searchResult.setCurrentPage(pageNo);
        List<T> data;
        int currentIndex = (pageNo - 1) * pageSize;
        if (pageNo * pageSize <= total) {
            data = list.subList(currentIndex, pageNo * pageSize);
        } else {
            data = list.subList(currentIndex, total);
        }
        searchResult.setData(data);
        return searchResult;
    }
}
